package com.gframework.sqlparam;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.gframework.util.GUtils;

import org.springframework.lang.Nullable;

/**
 * 一个操作条件封装类.
 * <p>请通过SqlParam类的静态方法取得本类对象。
 * <p>本类保存了有in或not int所需得集合参数，或者双/三目运算符所需的参数，参数会经过SqlParam严格验证后才会创建本类对象。
 * 如果value、column为null，或者集合长度为0，那么都不会产生本类对象。
 * 
 * @since 1.0.0
 * @author Ghwolf
 * 
 * @see SqlParam
 */
public class Param implements Serializable{
	private static final long serialVersionUID = -7328143680814525232L;
	/**
	 * sql中的单目运算符号(IS NULL,IS NOT NULL)
	 */
	@JsonIgnore
	static final int SIMPLE_OPERATOR = 0;
	/**
	 * sql中的双目运算符号
	 */
	@JsonIgnore
	static final int BINARY_OPERATOR = 1;
	/**
	 * sql中的in和not in
	 */
	@JsonIgnore
	static final int INT_NOTIN = 2;
	/**
	 * sql中的between...and
	 */
	@JsonIgnore
	static final int BETWEEN_AND = 3;
	/**
	 * 要进行匹配的列
	 */
	String column;
	/**
	 * 匹配的值
	 */
	Object value;
	/**
	 * 运算符
	 */
	String operator;
	/**
	 * 条件类型，主要用于生成mybatis的sql.<br>
	 * <li>1.双目运算</li>
	 * <li>2.IN/NOT IN</li>
	 * <li>3.BETWEEN AND</li>
	 */
	int type;

	Param() {
	}

	/**
	 * 取得一个双目以上运算符的Param对象
	 */
	Param(String column, Object value, String operator, int type) {
		this.column = column;
		this.value = value;
		this.operator = operator;
		this.type = type;
	}

	/**
	 * 取得一个单目运算符的Param对象
	 */
	Param(String column, String operator, int type) {
		this.column = column;
		this.operator = operator;
		this.type = type;
	}

	/**
	 * 取得当前条件的sql语句.
	 * <p>例如key=#{value}，这种最直接的条件语句。
	 * 其他返回值范例：
	 * <li>id in (#{!1},#{!2},#{!3})</li>
	 * <li>age between #{__start} and #{__end}</li>
	 * <li>age between ? and ?</li>
	 * 
	 * <p>需要注意的时，你需要尽量在外部维护一个NameGenerator生成器。
	 * 如果你单独使用Param类并且只有一个本类对象参与运算的时候，名称生成器时多余的，你可以传null。
	 * 但是如果你使用了多个Param，那么就可能出现预编译名称冲突问题，尤其是在in或not in语句生成的时候，必须依赖一个NameGenerator，
	 * 如果没有传递，则会自己实例化一个全新的NameGenerator接口对象，如果你用了两个Param，而且都有in或notin，那么就会出现问题。
	 * 
	 * @param param 存放编译参数值得集合，如果为null则不存放。
	 * @param ng where条件预编译参数名称生成器
	 * @param stringWrapper sql语句中的参数包装器，例如mybatis在sql语句中参数前后需要加上"#{}或${}"，但是在map中的参数名是不能够包含这些的，同时jdbc更不需要这些，为了兼容这些情况而需要此类对象
	 * @return 返回sql条件语句，如果无法生成一个有效的sql语句，则返回null
	 * 
	 * @see NameGenerator
	 */
	@Nullable
	public String getSql(@Nullable Map<String,Object> param,@Nullable NameGenerator ng,StringWrapper stringWrapper) {
		switch(this.type) {
			// 单目运算符
			case 0 : return this.getUnarySql();
			// 双目运算符
			case 1 : return this.getBinarySql(param, ng,stringWrapper);
			// in 或 not in运算符
			case 2 : return this.getInSql(param, ng,stringWrapper);
			// between and 三目运算符
			case 3 : return this.getTernarySql(param, ng,stringWrapper);
			default : return null;
		}
	}
	
	/**
	 * 按照mybatis风格取得当前条件的sql语句.
	 * <p>例如key=#{value}，这种最直接的条件语句。
	 * 其他返回值范例：
	 * <li>id in (#{!1},#{!2},#{!3})</li>
	 * <li>age between #{__start} and #{__end}</li>
	 * <li>age between ? and ?</li>
	 * 
	 * <p>需要注意的时，你需要尽量在外部维护一个NameGenerator生成器。
	 * 如果你单独使用Param类并且只有一个本类对象参与运算的时候，名称生成器时多余的，你可以传null。
	 * 但是如果你使用了多个Param，那么就可能出现预编译名称冲突问题，尤其是在in或not in语句生成的时候，必须依赖一个NameGenerator，
	 * 如果没有传递，则会自己实例化一个全新的NameGenerator接口对象，如果你用了两个Param，而且都有in或notin，那么就会出现问题。
	 * 
	 * @param param 存放编译参数值得集合，如果为null则不存放。
	 * @param ng where条件预编译参数名称生成器
	 * @return 返回sql条件语句，如果无法生成一个有效的sql语句，则返回null
	 * 
	 * @see Param#getSql(Map, NameGenerator, StringWrapper)
	 * @see NameGenerator
	 */
	@Nullable
	public String getSql(@Nullable Map<String,Object> param,@Nullable NameGenerator ng) {
		return this.getSql(param,ng,MybatisParamStringWrapper.get());
	}
	
	/**
	 * 取得单目运算符得条件语句.
	 * <p>当type=0
	 */
	private String getUnarySql() {
		return this.column + this.operator ;
	}
	/**
	 * 取得双目运算符得条件语句.
	 * <p>当type=1
	 */
	private String getBinarySql(@Nullable Map<String,Object> param,@Nullable NameGenerator ng,StringWrapper stringWrapper) {
		String name = ng == null ? this.column : ng.next();
		if (param != null) {
			param.put(name,this.value);
		}
		return this.column + this.operator + stringWrapper.wrapper(name);
	}
	/**
	 * 取得in 或 not in运算符得条件语句.
	 * <p>当type=2
	 */
	@Nullable
	private String getInSql(@Nullable Map<String,Object> param,@Nullable NameGenerator ng,StringWrapper stringWrapper) {
		if(ng == null) ng = new MybatisParamNameCacheGenerator();
			
		String inNotinSql ;
		if (this.value instanceof Collection) {
			inNotinSql = this.foreach((Collection<?>)this.value, param, ng,stringWrapper);
		} else if (this.value.getClass().isArray()) {
			inNotinSql = this.foreach(this.value, param, ng,stringWrapper);
		} else {
			inNotinSql = this.foreach(Arrays.asList(this.value), param, ng,stringWrapper);
		}
		return inNotinSql == null ? null : this.column + this.operator + inNotinSql;
	}
	/**
	 * 取得三目运算符得条件语句.
	 * <p>当type=3
	 */
	@Nullable
	private String getTernarySql(@Nullable Map<String,Object> param,@Nullable NameGenerator ng,StringWrapper stringWrapper) {
		String nameStart = ng == null ? "__start" : ng.next();
		String nameEnd = ng == null ? "__end" : ng.next();
		if (this.value instanceof BetweenAndQuery) {
			BetweenAndQuery<?> bq = (BetweenAndQuery<?>) this.value;
			if (param != null) {
				param.put(nameStart,bq.getBegin());
				param.put(nameEnd,bq.getEnd());
			}
			return this.column + " BETWEEN " + stringWrapper.wrapper(nameStart) + " AND " + stringWrapper.wrapper(nameEnd) ;
		} else {
			return null ;
		}
	}
	
	/**
	 * in和not in语句循环生成操作.
	 * <p>
	 * 返回值范例：(#{id1},#{id2},...)
	 * </p>
	 * 忽略null值
	 */
	protected String foreach(@Nullable Collection<?> collection,@Nullable Map<String,Object> param,NameGenerator ng,StringWrapper stringWrapper) {
		if (collection == null || collection.isEmpty()) return null;

		StringBuilder sql = new StringBuilder("(");
		for (Object obj : collection) {
			if (obj == null) continue;
			String n = ng.next();
			sql.append(stringWrapper.wrapper(n)).append(',');
			if (param != null) {
				param.put(n, obj);
			}
		}
		if (sql.length() == 1) {
			return null ;
		} else {
			return sql.delete(sql.length() - 1, sql.length()).append(')').toString();
		}
	}
	/**
	 * in和not in语句循环生成操作.
	 * <p>
	 * 返回值范例：(#{id1},#{id2},...)
	 * </p>
	 * 忽略null值
	 */
	protected String foreach(@Nullable Object array,@Nullable Map<String,Object> param,NameGenerator ng,StringWrapper stringWrapper) {
		if (array == null || !array.getClass().isArray() || Array.getLength(array) == 0) return null;
		
		StringBuilder sql = new StringBuilder("(");
		GUtils.arrayForeach(array, obj -> {
			if (obj == null) return;
			String n = ng.next();
			sql.append(stringWrapper.wrapper(n)).append(',');
			if (param != null) {
				param.put(n, obj);
			}
		});
		if (sql.length() == 1) {
			return null ;
		} else {
			return sql.delete(sql.length() - 1, sql.length()).append(')').toString();
		}
	}
	
	
	public String getOperator() {
		return this.operator;
	}

	public String getColumn() {
		return this.column;
	}

	public Object getValue() {
		return this.value;
	}

	public int getType() {
		return this.type;
	}

	@Override
	public String toString() {
		return "Param [column=" + column + ", value=" + value + ", operator=" + operator + ", type=" + type + "]";
	}

}